package twl.global.input;

import java.util.ArrayList;

import twl.global.widget.GlobalWidget;

public class GlobalGUI {
	
	private static final int DRAG_DIST = 5;
	private static final int DBLCLICK_TIME = 500;   // ms
	
	long curTime;
	
	final GlobalEvent event;
    private boolean dragActive;
    private int mouseClickCount;
    private int dragButton = -1;
    private int mouseDownX;
    private int mouseDownY;
    private int mouseLastX;
    private int mouseLastY;
    private int mouseClickedX;
    private int mouseClickedY;
    private long mouseClickedTime;
	
	private ArrayList<GlobalWidget> globalEventListenerList = new ArrayList<GlobalWidget>();
	
	public GlobalGUI() {
		event = new GlobalEvent();
	}
	
	public void addGlobalEventListener(GlobalWidget widget){
		globalEventListenerList.add(widget);
	}
	
	public void handleGlobalEvent(GlobalEvent evt){
		for(GlobalWidget widget : globalEventListenerList){
			widget.handleGlobalEvent(evt);
		}
	}
	
	private void sendEvent(GlobalEvent.Type type) {
        assert !type.isMouseEvent;
        event.type = type;
        event.dragEvent = false;
        handleGlobalEvent(event);
    }
	
	private void sendMouseEvent(GlobalEvent.Type type) {
        assert type.isMouseEvent;
        event.type = type;
        event.dragEvent = dragActive;
        handleGlobalEvent(event);
    }
	
	/**
     * A key was pressed or released. Keyboard events depend on the constants
     * of LWJGL's Keybaord class.
     *
     * Repeated key presses should be handled by {@code handleKeyRepeat} and not this
     * method so that the repeated flag is set correctly for the generated events.
     * 
     * @param keyCode the key code for this key or {@code Keyboard.KEY_NONE}
     * @param keyChar the unicode character resulting from this event or {@code Keyboard.CHAR_NONE}
     * @param pressed true if the key was pressed and false if it was released
     */
    public final void handleKey(int keyCode, char keyChar, boolean pressed) {
        event.keyCode = keyCode;
        event.keyChar = keyChar;
        event.keyRepeated = false;

        if(event.keyCode != GlobalEvent.KEY_NONE || event.keyChar != GlobalEvent.CHAR_NONE) {
            event.setModifiers(pressed);

            if(pressed) {
                sendEvent(GlobalEvent.Type.KEY_PRESSED);
            } else {
                sendEvent(GlobalEvent.Type.KEY_RELEASED);
            }
        }
    }
    
    
    /**
     * Mouse has moved / button was pressed or released.
     * 
     * @param mouseX the new mouse X coordinate
     * @param mouseY the new mouse Y coordinate
     * @param button the button that has been pressed/released or -1 if no button changed
     * @param pressed true if the button was pressed. Ignored if button is -1.
     */
    public final void handleMouse(int mouseX, int mouseY, int button, boolean pressed) {
        event.mouseButton = button;

        // only the previously pressed mouse button
        int prevButtonState = event.modifier & GlobalEvent.MODIFIER_BUTTON;
        
        int buttonMask = 0;
        switch (button) {
        case GlobalEvent.MOUSE_LBUTTON:
            buttonMask = GlobalEvent.MODIFIER_LBUTTON;
            break;
        case GlobalEvent.MOUSE_RBUTTON:
            buttonMask = GlobalEvent.MODIFIER_RBUTTON;
            break;
        case GlobalEvent.MOUSE_MBUTTON:
            buttonMask = GlobalEvent.MODIFIER_MBUTTON;
            break;
        }
        event.setModifier(buttonMask, pressed);
        boolean wasPressed = (prevButtonState & buttonMask) != 0;
        
        // don't send new mouse coords when still in drag area
        if(dragActive || prevButtonState == 0) {
            event.mouseX = mouseX;
            event.mouseY = mouseY;
        } else {
            event.mouseX = mouseDownX;
            event.mouseY = mouseDownY;
        }
        
        //MOUSE_MOVED AND MOUSE_DRAGGED ----------------------------------------
        if(mouseX != mouseLastX || mouseY != mouseLastY) {
            mouseLastX = mouseX;
            mouseLastY = mouseY;

            //Check if a button is click
            if(prevButtonState != 0 && !dragActive) {
                if(Math.abs(mouseX - mouseDownX) > DRAG_DIST ||
                    Math.abs(mouseY - mouseDownY) > DRAG_DIST) {
                    dragActive = true;
                    mouseClickCount = 0;
                }
            }
            
            if(dragActive) {
                sendMouseEvent(GlobalEvent.Type.MOUSE_DRAGGED);
            } else if(prevButtonState == 0) {
                sendMouseEvent(GlobalEvent.Type.MOUSE_MOVED);
            }
        }

        //MOUSE_BTNDOWN, MOUSE_BTNUP and MOUSE_CLICKED--------------------------
        if(buttonMask != 0 && pressed != wasPressed) {
        	
        	//MOUSE_BTNDOWN AND MOUSE_BTNUP-------------------------------------
            if(pressed) {
                if(dragButton < 0) {
                    mouseDownX = mouseX;
                    mouseDownY = mouseY;
                    dragButton = button;
                    sendMouseEvent(GlobalEvent.Type.MOUSE_BTNDOWN);
                } else {
                    sendMouseEvent(GlobalEvent.Type.MOUSE_BTNDOWN);
                }
            } else if(dragButton >= 0 && (event.isMouseDragEnd())) {
            	sendMouseEvent(GlobalEvent.Type.MOUSE_BTNUP);
            }

            //MOUSE_CLICKED-----------------------------------------------------
            if(!pressed && !dragActive) {
            	curTime = System.nanoTime()/(1000*1000);
            	if(mouseClickCount == 0 ||
            			curTime - mouseClickedTime > DBLCLICK_TIME) {
            		mouseClickedX = mouseX;
            		mouseClickedY = mouseY;
            		mouseClickCount = 0;
            		mouseClickedTime = curTime;
            	}
            	if(Math.abs(mouseX - mouseClickedX) < DRAG_DIST &&
            			Math.abs(mouseY - mouseClickedY) < DRAG_DIST) {
            		// ensure same click target as first
            		event.mouseX = mouseClickedX;
            		event.mouseY = mouseClickedY;
            		event.mouseClickCount = ++mouseClickCount;
            		mouseClickedTime = curTime;
            		sendMouseEvent(GlobalEvent.Type.MOUSE_CLICKED);
            	}
            }
        }

        if(event.isMouseDragEnd()) {
            if(dragActive) {
                dragActive = false;
                sendMouseEvent(GlobalEvent.Type.MOUSE_MOVED);
            }
            dragButton = -1;
        }
    }
    
    /**
     * Mouse wheel has been turned. Must be called after handleMouse.
     * 
     * @param wheelDelta the normalized wheel delta
     */
    public final void handleMouseWheel(int wheelDelta) {
        event.mouseWheelDelta = wheelDelta;
        sendMouseEvent(GlobalEvent.Type.MOUSE_WHEEL);
        event.mouseWheelDelta = 0;
    }
    
    

}
